package bloom_filter

import (
	"errors"
	"fmt"
	"github.com/bits-and-blooms/bloom/v3"
	cuckoo "github.com/seiflotfy/cuckoofilter"
	"os"
	"time"
)

type BloomError string

func (b BloomError) Error() string {
	return string(b)
}

const (
	NotImplementedError = BloomError("NotImplementedError")
)

type Filter interface {
	Add([]byte) error

	Delete([]byte) error

	Test([]byte) (bool, error)

	Reset() error
}

type BloomFilter struct {
	Name   string
	filter *bloom.BloomFilter
}

type cuckooFilter struct {
	Name   string
	filter *cuckoo.Filter
}

func NewBloomFilter(name string, m uint, k uint) *BloomFilter {
	return &BloomFilter{Name: name, filter: bloom.New(m, k)}
}

func GetBloomFilterConfig(bf *BloomFilter) (uint, uint) {
	return bf.filter.Cap(), bf.filter.K()
}

func (bf *BloomFilter) Load(filename string) error {
	f, err := os.Open(filename)

	if err != nil {
		return err
	}

	defer f.Close()

	_, err = bf.filter.ReadFrom(f)

	return err
}

func (f *BloomFilter) Reset() error {
	f.filter.ClearAll()
	return nil
}

func (f *BloomFilter) Add(buff []byte) error {
	f.filter.Add(buff)
	return nil
}

func (f *BloomFilter) Delete([]byte) error {
	return NotImplementedError
}

func (f *BloomFilter) Save(baseDir string) (string, error) {

	fileInfo, err := os.Stat(baseDir)
	if err != nil {

		if os.IsNotExist(err) {
			os.MkdirAll(baseDir, 0666)
		} else {
			return "", err
		}

	} else {
		if !fileInfo.IsDir() {
			return "", errors.New("base dir 不是目录")
		}

	}

	filename := fmt.Sprintf("%s%c%s_%d.dat", baseDir, os.PathSeparator, f.Name, time.Now().UnixNano())

	ff, err := os.Create(filename)

	if err != nil {
		return "", err
	}

	defer ff.Close()

	f.filter.WriteTo(ff)

	return filename, nil
}

func (f *BloomFilter) Test(buff []byte) (bool, error) {
	return f.filter.Test(buff), nil
}

func NewCuckooFilter(name string, cap uint) *cuckooFilter {
	return &cuckooFilter{Name: name, filter: cuckoo.NewFilter(cap)}
}

func (f *cuckooFilter) Add(buff []byte) error {
	f.filter.InsertUnique(buff)
	return nil
}

func (f *cuckooFilter) Delete(buff []byte) error {
	f.filter.Delete(buff)
	return nil
}

func (f *cuckooFilter) Test(buff []byte) (bool, error) {
	return f.filter.Lookup(buff), nil
}
func (f *cuckooFilter) Reset() error {
	f.filter.Reset()
	return nil
}

func (f *cuckooFilter) Save(baseDir string) (string, error) {
	return "", NotImplementedError
}
